flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_lib: bool,
flag_bin: Vec<String>,
flag_example: Vec<String>,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
-
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
let ops = ops::TestOptions {
no_run: options.flag_no_run,
no_fail_fast: false,
&options.flag_test,
&options.flag_example,
&options.flag_bench),
+ message_format: message_format,
target_rustdoc_args: None,
target_rustc_args: None,
},
flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_release: bool,
flag_lib: bool,
flag_bin: Vec<String>,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
&options.flag_test,
&options.flag_example,
&options.flag_bench),
+ message_format: message_format,
target_rustdoc_args: None,
target_rustc_args: None,
};
flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_package: Vec<String>,
flag_lib: bool,
flag_bin: Vec<String>,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
&empty,
&empty,
&empty),
+ message_format: message_format,
release: options.flag_release,
mode: ops::CompileMode::Doc {
deps: !options.flag_no_deps,
release: !options.flag_debug,
filter: ops::CompileFilter::new(false, &options.flag_bin, &[],
&options.flag_example, &[]),
+ message_format: ops::MessageFormat::Human,
target_rustc_args: None,
target_rustdoc_args: None,
};
flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_release: bool,
flag_frozen: bool,
flag_locked: bool,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
bins: &bins, examples: &examples,
}
},
+ message_format: message_format,
target_rustdoc_args: None,
target_rustc_args: None,
};
flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_release: bool,
flag_lib: bool,
flag_bin: Vec<String>,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
let root = try!(find_root_manifest_for_wd(options.flag_manifest_path,
config.cwd()));
&options.flag_test,
&options.flag_example,
&options.flag_bench),
+ message_format: message_format,
target_rustdoc_args: None,
target_rustc_args: options.arg_opts.as_ref().map(|a| &a[..]),
};
flag_release: bool,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_package: Option<String>,
flag_lib: bool,
flag_bin: Vec<String>,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
let root = try!(find_root_manifest_for_wd(options.flag_manifest_path,
config.cwd()));
&options.flag_test,
&options.flag_example,
&options.flag_bench),
+ message_format: message_format,
mode: ops::CompileMode::Doc { deps: false },
target_rustdoc_args: Some(&options.arg_opts),
target_rustc_args: None,
flag_verbose: u32,
flag_quiet: Option<bool>,
flag_color: Option<String>,
+ flag_message_format: Option<String>,
flag_release: bool,
flag_no_fail_fast: bool,
flag_frozen: bool,
-v, --verbose ... Use verbose output
-q, --quiet No output printed to stdout
--color WHEN Coloring: auto, always, never
+ --message-format FMT Error format: human, json-v1
--no-fail-fast Run all tests regardless of failure
--frozen Require Cargo.lock and cache are up to date
--locked Require Cargo.lock is up to date
&options.flag_color,
options.flag_frozen,
options.flag_locked));
+ let message_format = try!(ops::MessageFormat::from_option(
+ &options.flag_message_format
+ ));
+
let root = try!(find_root_manifest_for_wd(options.flag_manifest_path, config.cwd()));
let empty = Vec::new();
release: options.flag_release,
mode: mode,
filter: filter,
+ message_format: message_format,
target_rustdoc_args: None,
target_rustc_args: None,
},
pub release: bool,
/// Mode for this compile.
pub mode: CompileMode,
+ /// `--error_format` flag for the compiler.
+ pub message_format: MessageFormat,
/// Extra arguments to be passed to rustdoc (for main crate and dependencies)
pub target_rustdoc_args: Option<&'a [String]>,
/// The specified target will be compiled with all the available arguments,
Doc { deps: bool },
}
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum MessageFormat {
+ Human,
+ Json
+}
+
+impl MessageFormat {
+ pub fn from_option(opt: &Option<String>) -> CargoResult<MessageFormat> {
+ match opt.as_ref().map(|s| s.as_ref()) {
+ None | Some("human") => Ok(MessageFormat::Human),
+ Some("json-v1") => Ok(MessageFormat::Json),
+ Some(other) => bail!("argument for --message-format must be human or json-v1, \
+ but found `{}`", other)
+ }
+ }
+}
+
pub enum CompileFilter<'a> {
Everything,
Only {
let root_package = try!(ws.current());
let CompileOptions { config, jobs, target, spec, features,
all_features, no_default_features,
- release, mode,
+ release, mode, message_format,
ref filter, ref exec_engine,
ref target_rustdoc_args,
ref target_rustc_args } = *options;
build_config.exec_engine = exec_engine.clone();
build_config.release = release;
build_config.test = mode == CompileMode::Test;
+ build_config.json_errors = message_format == MessageFormat::Json;
if let CompileMode::Doc { deps } = mode {
build_config.doc_all = deps;
}
filter: ops::CompileFilter::Everything,
exec_engine: None,
release: false,
+ message_format: ops::MessageFormat::Human,
mode: ops::CompileMode::Build,
target_rustdoc_args: None,
target_rustc_args: None,
use std::path::{self, PathBuf};
use std::sync::Arc;
+use rustc_serialize::json;
+
use core::{Package, PackageId, PackageSet, Target, Resolve};
use core::{Profile, Profiles, Workspace};
use core::shell::ColorConfig;
pub release: bool,
pub test: bool,
pub doc_all: bool,
+ pub json_errors: bool,
}
#[derive(Clone, Default)]
}
}
let has_custom_args = unit.profile.rustc_args.is_some();
- let exec_engine = cx.exec_engine.clone();
let filenames = try!(cx.target_filenames(unit));
let root = cx.out_dir(unit);
let cwd = cx.config.cwd().to_path_buf();
rustc.args(&try!(cx.rustflags_args(unit)));
-
+ let json_errors = cx.build_config.json_errors;
+ let package_id = unit.pkg.package_id().clone();
+ let target = unit.target.clone();
return Ok(Work::new(move |state| {
// Only at runtime have we discovered what the extra -L and -l
// arguments are for native libraries, so we process those here. We
}
state.running(&rustc);
- try!(exec_engine.exec(rustc).chain_error(|| {
+ let process_builder = rustc.into_process_builder();
+ try!(if json_errors {
+ #[derive(RustcEncodable)]
+ struct Message<'a> {
+ reason: &'a str,
+ package_id: &'a PackageId,
+ target: &'a Target,
+ message: json::Json,
+ }
+ process_builder.exec_with_streaming(
+ &mut |line| assert!(line.is_empty()),
+ &mut |line| {
+ let rustc_message = json::Json::from_str(line).unwrap_or_else(|_| {
+ panic!("Compiler produced invalid json: `{}`", line)
+ });
+
+ let message = Message {
+ reason: "rustc-message",
+ package_id: &package_id,
+ target: &target,
+ message: rustc_message,
+ };
+ let encoded = json::encode(&message).unwrap();
+ println!("{}", encoded);
+
+ },
+ ).map(|_| ())
+ } else {
+ process_builder.exec()
+ }.chain_error(|| {
human(format!("Could not compile `{}`.", name))
}));
cmd.arg("--color").arg(&color_config.to_string());
}
+ if cx.build_config.json_errors {
+ cmd.arg("--error-format").arg("json");
+ }
+
cmd.arg("--crate-name").arg(&unit.target.crate_name());
if !test {
pub use self::cargo_clean::{clean, CleanOptions};
pub use self::cargo_compile::{compile, compile_ws, resolve_dependencies, CompileOptions};
-pub use self::cargo_compile::{CompileFilter, CompileMode};
+pub use self::cargo_compile::{CompileFilter, CompileMode, MessageFormat};
pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages};
pub use self::cargo_rustc::{compile_targets, Compilation, Layout, Kind, Unit};
pub use self::cargo_rustc::{Context, LayoutProxy};
"));
}
+#[test]
+fn compiler_json_error_format() {
+ if !is_nightly() { return }
+
+ let p = project("foo")
+ .file("Cargo.toml", r#"
+ [project]
+
+ name = "foo"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+
+ [dependencies.bar]
+ path = "bar"
+ "#)
+ .file("src/main.rs", "fn main() { let unused = 92; }")
+ .file("bar/Cargo.toml", r#"
+ [project]
+
+ name = "bar"
+ version = "0.5.0"
+ authors = ["wycats@example.com"]
+ "#)
+ .file("bar/src/lib.rs", r#"fn dead() {}"#);
+
+ assert_that(p.cargo_process("build").arg("-v")
+ .arg("--message-format").arg("json-v1"),
+ execs().with_json(r#"
+ {
+ "reason":"rustc-message",
+ "package_id":"bar 0.5.0 ([..])",
+ "target":{"kind":["lib"],"name":"bar","src_path":"[..]lib.rs"},
+ "message":{
+ "children":[],"code":null,"level":"warning","rendered":null,
+ "message":"function is never used: `dead`, #[warn(dead_code)] on by default",
+ "spans":[{
+ "byte_end":12,"byte_start":0,"column_end":13,"column_start":1,"expansion":null,
+ "file_name":"[..]","is_primary":true,"label":null,"line_end":1,"line_start":1,
+ "suggested_replacement":null,
+ "text":[{"highlight_end":13,"highlight_start":1,"text":"fn dead() {}"}]
+ }]
+ }
+ }
+
+ {
+ "reason":"rustc-message",
+ "package_id":"foo 0.5.0 ([..])",
+ "target":{"kind":["bin"],"name":"foo","src_path":"[..]main.rs"},
+ "message":{
+ "children":[],"code":null,"level":"warning","rendered":null,
+ "message":"unused variable: `unused`, #[warn(unused_variables)] on by default",
+ "spans":[{
+ "byte_end":22,"byte_start":16,"column_end":23,"column_start":17,"expansion":null,
+ "file_name":"[..]","is_primary":true,"label":null,"line_end":1,"line_start":1,
+ "suggested_replacement":null,
+ "text":[{"highlight_end":23,"highlight_start":17,"text":"[..]"}]
+ }]
+ }
+ }
+"#));
+}
+
+#[test]
+fn wrong_message_format_option() {
+ let p = project("foo")
+ .file("Cargo.toml", &basic_bin_manifest("foo"))
+ .file("src/main.rs", "fn main() {}");
+ p.build();
+
+ assert_that(p.cargo_process("build").arg("--message-format").arg("XML"),
+ execs().with_status(101)
+ .with_stderr_contains("\
+[ERROR] argument for --message-format must be human or json-v1, but found `XML`"));
+}
+
#[test]
fn no_warn_about_package_metadata() {
let p = project("foo")
expect_exit_code: Option<i32>,
expect_stdout_contains: Vec<String>,
expect_stderr_contains: Vec<String>,
- expect_json: Option<Json>,
+ expect_json: Option<Vec<Json>>,
}
impl Execs {
}
pub fn with_json(mut self, expected: &str) -> Execs {
- self.expect_json = Some(Json::from_str(expected).unwrap());
+ self.expect_json = Some(expected.split("\n\n").map(|obj| {
+ Json::from_str(obj).unwrap()
+ }).collect());
self
}
&actual.stdout, true));
}
- if let Some(ref expect_json) = self.expect_json {
- try!(self.match_json(expect_json, &actual.stdout));
+ if let Some(ref objects) = self.expect_json {
+ let lines = match str::from_utf8(&actual.stdout) {
+ Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
+ Ok(stdout) => stdout.lines().collect::<Vec<_>>(),
+ };
+ if lines.len() != objects.len() {
+ return Err(format!("expected {} json lines, got {}",
+ objects.len(), lines.len()));
+ }
+ for (obj, line) in objects.iter().zip(lines) {
+ try!(self.match_json(obj, line));
+ }
}
Ok(())
}
}
- fn match_json(&self, expected: &Json, stdout: &[u8]) -> ham::MatchResult {
- let stdout = match str::from_utf8(stdout) {
- Err(..) => return Err("stdout was not utf8 encoded".to_owned()),
- Ok(stdout) => stdout,
- };
-
- let actual = match Json::from_str(stdout) {
- Err(..) => return Err(format!("Invalid json {}", stdout)),
+ fn match_json(&self, expected: &Json, line: &str) -> ham::MatchResult {
+ let actual = match Json::from_str(line) {
+ Err(e) => return Err(format!("invalid json, {}:\n`{}`", e, line)),
Ok(actual) => actual,
};